﻿using System;
using System.IO;
using System.Net;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
using System.Text;

namespace BrasilFramework.Comum.Comunicacao
{
    public class FTP
    {

        private const int TAMANHO_BUFFER = 2048;
        private const string FORMATO_ENDERECO = "{0}://{1}/{2}";

        public string Servidor { get; set; }
        public bool Seguro { get; set; }
        public string Usuario { get; set; }
        public string Senha { get; set; }

        public static bool ValidarCertificado(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
        {
            switch (sslPolicyErrors)
            {
                case SslPolicyErrors.RemoteCertificateChainErrors:
                    return false;
                case SslPolicyErrors.RemoteCertificateNameMismatch:
                    {
                        System.Security.Policy.Zone z =
                            System.Security.Policy.Zone.CreateFromUrl(((HttpWebRequest)sender).RequestUri.ToString());
                        return z.SecurityZone == System.Security.SecurityZone.Intranet || z.SecurityZone == System.Security.SecurityZone.MyComputer;
                    }
            }
            return true;
        }


        private FtpWebRequest CriarRequisicao(string caminho)
        {
            FtpWebRequest requisicaoFTP =
                (FtpWebRequest)
                WebRequest.Create(new Uri(String.Format(FORMATO_ENDERECO, "ftp", Servidor, caminho)));

            requisicaoFTP.Credentials = new NetworkCredential(Usuario, Senha);
            requisicaoFTP.EnableSsl = Seguro;
            requisicaoFTP.Proxy = null;

            //Validar certificado
            if (Seguro) ServicePointManager.ServerCertificateValidationCallback = ValidarCertificado;

            return requisicaoFTP;
        }

        /// <summary>
        ///   Envia o arquivo especificado para o servidor FTP.
        /// </summary>
        /// <param name = "caminhoOrigem">Caminho do arquivo de origem a ser enviado.</param>
        /// <param name = "caminhoDestino">Caminho onde será gravado o arquivo.</param>
        public void Enviar(string caminhoOrigem, string caminhoDestino)
        {
            using (FileStream fs = new FileStream(caminhoDestino, FileMode.Open))
            {
                Enviar(fs, caminhoDestino);
            }
        }

        /// <summary>
        ///   Envia o arquivo especificado para o servidor FTP.
        /// </summary>
        /// <param name="stream"></param>
        /// <param name = "caminhoDestino">Caminho onde será gravado o arquivo.</param>
        public void Enviar(Stream stream, string caminhoDestino)
        {
            //Cria uma requisição FTP.
            FtpWebRequest requisicaoFTP = CriarRequisicao(caminhoDestino);

            //Tipo de comando a ser executado.
            requisicaoFTP.Method = TipoComando.EnviarArquivo;

            //Tipo de transferência.
            requisicaoFTP.UseBinary = true;

            //Informa ao servidor qual é o tamanho do arquivo a ser enviado.
            requisicaoFTP.ContentLength = stream.Length;

            //Configura tamanho do buffer.
            byte[] buffer = new byte[TAMANHO_BUFFER];

            //Escreve o arquivo na memória para envio.
            Stream arquivoServidor = requisicaoFTP.GetRequestStream();

            //Ler o arquivo de acordo com o tamanho do buffer
            int quantidadeLida = stream.Read(buffer, 0, TAMANHO_BUFFER);

            // Repete leitura até o fim do arquivo
            while (quantidadeLida != 0)
            {
                //Ler o arquivo físico e envia para o servidor
                arquivoServidor.Write(buffer, 0, quantidadeLida);
                quantidadeLida = stream.Read(buffer, 0, TAMANHO_BUFFER);
            }

            //Fecha os arquivos.
            arquivoServidor.Close();
        }

        /// <summary>
        ///   Baixa o arquivo especificado do servidor FTP.
        /// </summary>
        /// <param name = "caminhoOrigem">Caminho do arquivo de origem a ser recuperado.</param>
        public Stream Baixar(string caminhoOrigem)
        {
            return Baixar(caminhoOrigem, string.Empty);
        }

        /// <summary>
        ///   Baixa o arquivo especificado do servidor FTP.
        /// </summary>
        /// <param name = "caminhoOrigem">Caminho do arquivo de origem a ser recuperado.</param>
        /// <param name = "caminhoDestino">Caminho onde será gravado o arquivo.</param>
        public Stream Baixar(string caminhoOrigem, string caminhoDestino)
        {
            //Cria uma requisição FTP.
            FtpWebRequest requisicaoFTP = CriarRequisicao(caminhoOrigem);

            //Tipo de comando a ser executado.
            requisicaoFTP.Method = WebRequestMethods.Ftp.DownloadFile;

            //Tipo de transferência.
            requisicaoFTP.UseBinary = true;

            //Configura tamanho do buffer.
            byte[] buffer = new byte[TAMANHO_BUFFER];

            //Cria escuta para receber o arquivo.
            FtpWebResponse escutaServidor = (FtpWebResponse)requisicaoFTP.GetResponse();

            //Escreve o arquivo na memória para envio.
            Stream arquivoServidor = escutaServidor.GetResponseStream();

            if (arquivoServidor == null) return null;

            //Ler o arquivo de acordo com o tamanho do buffer.
            int quantidadeLida = arquivoServidor.Read(buffer, 0, TAMANHO_BUFFER);

            if (string.IsNullOrEmpty(caminhoDestino))
            {
                MemoryStream memoria = new MemoryStream();

                while (quantidadeLida > 0)
                {
                    memoria.Write(buffer, 0, quantidadeLida);
                    quantidadeLida = arquivoServidor.Read(buffer, 0, TAMANHO_BUFFER);
                }

                arquivoServidor.Close();
                escutaServidor.Close();

                //Posiciona o início da memória para o primeiro byte. Isto evitará a leitura da memória pelo fim.
                memoria.Seek(0, SeekOrigin.Begin);
                return memoria;
            }

            //Cria arquivo físico a ser preenchido.
            FileStream arquivoFisico = new FileStream(caminhoDestino, FileMode.Create);

            while (quantidadeLida > 0)
            {
                arquivoFisico.Write(buffer, 0, quantidadeLida);
                quantidadeLida = arquivoServidor.Read(buffer, 0, TAMANHO_BUFFER);
            }

            arquivoFisico.Close();
            escutaServidor.Close();

            return arquivoFisico;
        }

        private string[] Listar(string tipoComando, string caminho)
        {
            StringBuilder resultado = new StringBuilder();
            FtpWebRequest requisicaoFTP = CriarRequisicao(caminho);
            requisicaoFTP.Method = tipoComando;

            WebResponse escutaFTP = requisicaoFTP.GetResponse();

            StreamReader memoriaLeitura = new StreamReader(escutaFTP.GetResponseStream());
            string line = memoriaLeitura.ReadLine();

            while (line != null)
            {
                resultado.Append(line);
                resultado.Append("\n");
                line = memoriaLeitura.ReadLine();
            }

            int indiceFinal = resultado.ToString().LastIndexOf("\n");
            if (indiceFinal == -1) return new string[0];
                
            resultado.Remove(indiceFinal, 1);

            memoriaLeitura.Close();
            escutaFTP.Close();

            return resultado.ToString().Split('\n');

        }

        /// <summary>
        ///   Lista todos arquivos do caminho
        /// </summary>
        /// <param name = "caminho">Caminho do diretório que deverá ser listado.</param>
        /// <returns></returns>
        public string[] Listar(string caminho = "")
        {
            return Listar(TipoComando.Listar, caminho);
        }

        /// <summary>
        ///   Lista todos arquivos do caminho com detalhes
        /// </summary>
        /// <param name = "caminho">Caminho do diretório que deverá ser listado.</param>
        /// <returns></returns>
        public string[] ListarDetalhe(string caminho = "")
        {
            return Listar(TipoComando.ListarDetalhe, caminho);
        }

        #region Nested type: TipoComando

        public static class TipoComando
        {
            public const string Listar = WebRequestMethods.Ftp.ListDirectory;
            public const string ListarDetalhe = WebRequestMethods.Ftp.ListDirectoryDetails;
            public const string BaixarArquivo = WebRequestMethods.Ftp.DownloadFile;
            public const string EnviarArquivo = WebRequestMethods.Ftp.UploadFile;
        }

        #endregion
    }
}